package cn.itsource.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import cn.itsource.constants.CacheConstants;
import cn.itsource.constants.MQConstants;
import cn.itsource.constants.OrderStatus;
import cn.itsource.constants.RConst;
import cn.itsource.domain.DriverSetting;
import cn.itsource.domain.Login;
import cn.itsource.exception.GlobalException;
import cn.itsource.mapper.OrderBillMapper;
import cn.itsource.api.openfeign.IRuleApiToService;
import cn.itsource.pojo.domain.Order;
import cn.itsource.mapper.OrderMapper;
import cn.itsource.pojo.domain.OrderBill;
import cn.itsource.pojo.dto.ExpectPriceDto;
import cn.itsource.pojo.dto.GeoSearchResultDto;
import cn.itsource.pojo.dto.PushOrder;
import cn.itsource.pojo.ro.CreateOrderRo;
import cn.itsource.pojo.ro.RuleRo;
import cn.itsource.pojo.vo.ExecuteOrderVo;
import cn.itsource.result.JSONResult;
import cn.itsource.service.IOrderService;
import cn.itsource.utils.AssertUtil;
import cn.itsource.utils.CodeGenerateUtils;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * 订单表 服务实现类
 * </p>
 *
 * @author zls
 * @since 2024-07-19
 */
@Service
@Transactional
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements IOrderService {

    @Autowired
    private IRuleApiToService ruleOpenFeignClient;
    @Autowired
    private OrderBillMapper orderBillMapper;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Override
    public Order inProcess() {
        Long id = Long.valueOf(StpUtil.getLoginId().toString());
        List<Order> list = super.list(new LambdaQueryWrapper<Order>()
                .eq(Order::getCustomerId, id)
                .in(Order::getStatus, RConst.Num.INT_0,
                        RConst.Num.INT_1, RConst.Num.INT_2, RConst.Num.INT_3)
                .orderByAsc(Order::getCreateTime));
        if (list.isEmpty()){
            return null;
        }
        return list.get(RConst.Num.INT_0);
    }

    @Override
    public String create(CreateOrderRo createOrderRo) {
        Date date = new Date();
        //计算预计费用  一定有人给你 表格
        JSONResult<ExpectPriceDto> bigDecimalJSONResult =
                ruleOpenFeignClient.saveRule(new RuleRo(date, createOrderRo.getExpectsMileage()));
        if (!bigDecimalJSONResult.isSuccess()) throw new GlobalException(bigDecimalJSONResult.getMessage());
        ExpectPriceDto data = bigDecimalJSONResult.getData();
        //保存订单
        Order order = buildOrder(createOrderRo, date, data.getSumPrice());
        //保存账单
        OrderBill bill = buildOrderBill(order, data);
        super.save(order);
        orderBillMapper.insert(bill);
        //为什么要在这里去获取司机的定位？
        //redis 里面geo已经帮你做好了 你只需要调用API
        //符合条件 范围内的司机
        List<GeoSearchResultDto> geoSearchResults = this.searchGeo(CacheConstants.KEY_DRIVER_GEO,
                Double.valueOf(createOrderRo.getStartPlaceLongitude()), Double.valueOf(createOrderRo.getStartPlaceLatitude()),
                RConst.Num.DOUBLE_5, RConst.Num.BASE_LONG_5);
        //附近没有司机
        AssertUtil.isFalse(geoSearchResults.isEmpty(),"附近没有对应代驾");
        //发送对应的消息给司机
        sendDriverMq(createOrderRo, order, geoSearchResults);

        return order.getOrderNo();
    }
    @Override
    public Object cancelAuto(String orderNo) {
        //必须是自己的订单
        Object loginId = StpUtil.getLoginId();
        //这个订单必须是
        //修改状态
        Order order = new Order();
        order.setStatus(OrderStatus.ORDER_STATUS_CUSTOMER_CANCEL_ORDER.getCode());
        order.setUpdateTime(new Date());
        return super.update(order, new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo)
                .eq(Order::getCustomerId, Long.valueOf(loginId.toString()))
                .eq(Order::getStatus, OrderStatus.ORDER_STATUS_WAIT.getCode()));
    }

    @Override
    public Object cancelHand(String orderNo) {
        //必须是自己的订单
        Object loginId = StpUtil.getLoginId();
        LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNo, orderNo)
                .eq(Order::getCustomerId, Long.valueOf(loginId.toString()))
                .in(Order::getStatus,OrderStatus.ORDER_STATUS_WAIT.getCode(),OrderStatus.ORDER_STATUS_ACCEPTED.getCode(),OrderStatus.ORDER_STATUS_ARRIVE.getCode(),OrderStatus.ORDER_STATUS_START_DRIVING.getCode());
        Order one = super.getOne(wrapper);
        //-------------业务处理
        Integer status = one.getStatus();
        //分析  0等待接单，1已接单，2司机已到达，3开始代驾 要去查询出来
        //规则表 --不同的状态用户赔偿的钱是不一样
        // 0 所谓
        // 1 预订单的10% 指定一个最高价格
        // 2 预订单的30% 指定一个最高价格
        // 3 非常复杂 获取司机的定位 计算开始到现在这个位置 计算价钱+返程
        // 特殊情况  用一张表来记录用户的信用
        //--------------业务处理
        one.setUpdateTime(new Date());
        one.setStatus(OrderStatus.ORDER_STATUS_CUSTOMER_CANCEL_ORDER.getCode());
        return super.updateById(one);
    }

    @Override
    public Object grabOrders(String orderNo) {
        //获取锁
        RLock lock = redissonClient.getLock(String.format(CacheConstants.LOCK_GRAB_ORDER, orderNo));
        try {
            lock.lock();//上锁
            Order one = super.getOne(new LambdaQueryWrapper<Order>()
                    .eq(Order::getOrderNo, orderNo)
                    .eq(Order::getStatus,OrderStatus.ORDER_STATUS_WAIT.getCode()));
            AssertUtil.isNotNull(one,"抢单失败");
            Login userinfo = JSONObject.parseObject(StpUtil.getExtra("userinfo").toString(), Login.class);
            one.setDriverId(userinfo.getId());
            one.setAcceptTime(new Date());
            one.setStatus(OrderStatus.ORDER_STATUS_ACCEPTED.getCode());
            one.setDriverName(userinfo.getNickName());
            one.setDriverPhoto(userinfo.getAvatar());
            one.setDriverPhone(userinfo.getPhone());
            super.updateById(one);
            //返回订单信息
            return ExecuteOrderVo.orderToOrderVo(one);
        } finally {
            lock.unlock();
        }

    }

    private void sendDriverMq(CreateOrderRo createOrderRo, Order order, List<GeoSearchResultDto> geoSearchResults) {
        for (GeoSearchResultDto geoSearchResult : geoSearchResults) {
            String name = geoSearchResult.getName();
            //拿到缓存当中的司机配置
            String key = String.format(CacheConstants.KEY_DRIVER_ONLINE_SETTING, name);
            DriverSetting setting = (DriverSetting) redisTemplate.opsForValue().get(key);
            //没上线
            if (setting==null)continue;
            //超出接单范围
            if (geoSearchResult.getDistance()>setting.getRangeDistance())continue;
            //上线但是没有开启 自动听单
            if (!setting.getListenService())continue;
            //订单预计公里不能大于设置的
            if (order.getExpectsMileage().intValue()>setting.getOrderDistance())continue;
            //要推送给司机的内容
            PushOrder pushOrder = new PushOrder();
            pushOrder.setDistance(geoSearchResult.getDistance());//怎么算出来
            pushOrder.setMileage(order.getExpectsMileage().doubleValue());
            pushOrder.setExpectsFee(order.getExpectsOrderAmount().doubleValue());
            if(createOrderRo.getFavourAmount()!=null){
                pushOrder.setFavourFee(createOrderRo.getFavourAmount().doubleValue());
            }
            pushOrder.setFromAddressName(createOrderRo.getStartPlace());
            pushOrder.setFromLongitude(createOrderRo.getStartPlaceLongitude());
            pushOrder.setFromLatitude(createOrderRo.getStartPlaceLatitude());

            pushOrder.setToAddressName(createOrderRo.getEndPlace());
            pushOrder.setToLongitude(createOrderRo.getEndPlaceLongitude());
            pushOrder.setToLatitude(createOrderRo.getEndPlaceLatitude());

            //指定发给某位司机 自己pull
            String topic = MQConstants.PUSH_ORDER_TO_DRIVER_TOPIC+":"+String.format(MQConstants.PUSH_ORDER_TO_DRIVER_TAG,name);
            SendResult sendResult = rocketMQTemplate.syncSend(topic,MessageBuilder.withPayload(pushOrder).build());
            //判断是否发送成功

            //如何防止消息投递失败
            //方案  去记录数据库 开启定时任务 去重试(可能也会失败 在记录次数10 给管理员发短信)
            if(sendResult.getSendStatus() != SendStatus.SEND_OK) continue;
        }
    }

    private List<GeoSearchResultDto> searchGeo(String keyDriverGeo, Double startPlaceLongitude, Double startPlaceLatitude,
                           Double key, long limit) {
        //搜索订单起始点5公里以内的司机
        Point point = new Point(startPlaceLongitude,startPlaceLatitude);
        //设置GEO距离单位为千米
        Metric metric = RedisGeoCommands.DistanceUnit.KILOMETERS;
        Distance distance = new Distance(key, metric);
        Circle circle = new Circle(point, distance);

        //创建GEO参数
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs
                .newGeoRadiusArgs();
                //结果中包含 你需要哪些返回结果
                args.includeDistance()//距离
                //结果中包含坐标
                .includeCoordinates()//坐标
                //按照中心点距离近到远排序
                .sortAscending()//排序
                //匹配前几个司机
                .limit(limit);//要多少个

        //执行GEO计算，获得查询结果
        GeoResults<RedisGeoCommands.GeoLocation<Object>> radius = redisTemplate.opsForGeo()
                .radius(keyDriverGeo, circle, args);
        List<GeoSearchResultDto> list = new ArrayList<>();
        List<GeoResult<RedisGeoCommands.GeoLocation<Object>>> content = radius.getContent();
        //循环当前范围的的司机 并返回
        for (GeoResult<RedisGeoCommands.GeoLocation<Object>> geoLocationGeoResult : content) {
            RedisGeoCommands.GeoLocation<Object> content2 = geoLocationGeoResult.getContent();
            GeoSearchResultDto geoSearchResult = new GeoSearchResultDto();
            geoSearchResult.setName(content2.getName().toString());
            geoSearchResult.setLongitude(content2.getPoint().getX());
            geoSearchResult.setLatitude(content2.getPoint().getY());
            geoSearchResult.setDistance(geoLocationGeoResult.getDistance().getValue());
            list.add(geoSearchResult);
        }
        return list;
    }

    private OrderBill buildOrderBill(Order order, ExpectPriceDto data) {
        OrderBill bill = new OrderBill();
        bill.setId(IdUtil.createSnowflake(RConst.Num.INT_1, RConst.Num.INT_1).nextId());
        bill.setOrderId(order.getId());
        //账单总金额怎么计算 = 实际公里数 + 打赏 + 返程 + 平台奖励
//        bill.setRealOrderAmount();
//        bill.setRealPayAmount();
//        走高速才会出
//        bill.setTollAmount();
        bill.setFavourAmount(order.getFavourAmount());
//        bill.setIncentiveAmount();
//        bill.setVoucherAmount();
        StringBuilder sb = new StringBuilder();
        String detail = sb.append("乘客").append(order.getCustomerName()).append("从")
                .append(order.getStartPlace()).append("到").append(order.getEndPlace()).toString();
        bill.setOrderDetail(detail);

        bill.setBaseMileage(data.getBaseMileage());
        bill.setBaseMileageAmount(data.getBaseMileageAmount());
        bill.setExceedBaseMileageEveryKmAmount(data.getExceedBaseMileageEveryKmAmount());
        bill.setExceedBaseMileage(data.getExceedBaseMileage());
        bill.setExceedBaseMileageAmount(data.getExceedBaseMileageAmount());
        bill.setMileageAmount(data.getMileageAmount());
//        bill.setWaitingMinute();
//        bill.setFreeBaseWaitingMinute();
//        bill.setExeceedBaseWaitingEveryKmAmount();
//        bill.setExeceedBaseWaitingMinute();
//        bill.setWaitingAmount();
        bill.setReturnMileage(data.getReturnMileage());
        bill.setFreeBaseReturnMileage(data.getFreeBaseReturnMileage());
        bill.setExceedBaseReturnEveryKmAmount(data.getExceedBaseReturnEveryKmAmount());
        bill.setExceedFreeBaseReturnMileage(data.getExceedFreeBaseReturnMileage());
        bill.setReturnAmont(data.getReturnAmont());
        bill.setOrderNo(order.getOrderNo());
//        bill.setOtherAmount();
//        bill.setParkingAmount();
        bill.setCreateTime(order.getCreateTime());
        return bill;
    }

    private Order buildOrder(CreateOrderRo createOrderRo, Date date, BigDecimal expectsOrderAmount) {
        Login userinfo = JSONObject.parseObject(StpUtil.getExtra("userInfo").toString(), Login.class);
        //创建订单保存数据库
        Order order = CreateOrderRo.orderRoToOrder(createOrderRo);
        order.setCreateTime(new Date());
        order.setId(IdUtil.createSnowflake(RConst.Num.INT_1, RConst.Num.INT_1).nextId());
        order.setOrderNo(CodeGenerateUtils.generateOrderSn(userinfo.getId()));//订单编号
        order.setCreateTime(date);
        order.setStatus(RConst.Num.INT_0);
        //乘客的信息
        order.setCustomerId(userinfo.getId());
        order.setCustomerName(userinfo.getName());
        order.setCustomerName(userinfo.getUsername());
        order.setCustomerPhone(userinfo.getPhone());
        order.setCustomerPhoto(userinfo.getAvatar());

        order.setExpectsOrderAmount(expectsOrderAmount);
        return order;
    }
}
